package me.stone.stanimeclient.core.ui.player

import android.annotation.SuppressLint
import android.content.Context
import android.view.GestureDetector
import android.view.MotionEvent
import androidx.annotation.OptIn
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.SeekParameters
import androidx.media3.ui.PlayerView
import me.stone.stanimeclient.core.common.extensions.dip
import me.stone.stanimeclient.core.ui.R
import java.util.concurrent.TimeUnit
import kotlin.math.abs

@SuppressLint("ClickableViewAccessibility")
@OptIn(UnstableApi::class)
class AgeExoPlayerGestureHelper private constructor(
    private val playerPanel: AgeExoPlayerUIBinding,
    private val volumeManager: VolumeManager,
    private val brightnessManager: BrightnessManager
) {

    private val playerView: PlayerView
        get() = playerPanel.playerView

    private val timeBar: AgePlayerTimeBar
        get() = playerPanel.timeBar

    private val isLocked: Boolean
        get() = playerPanel.isLockState

    private val context: Context
        get() = playerView.context

    private var currentGestureAction: GestureAction? = null
    private var currentPlaySpeed: Float? = null

    private var seekStartPos: Long? = null
    private var seekEndedPos: Long? = null
    private var seekStartWithPlaying = false

    private val tapGestureDetector = GestureDetector(
        context, object : GestureDetector.SimpleOnGestureListener() {
            override fun onSingleTapConfirmed(e: MotionEvent): Boolean {
                with(playerView) {
                    if (isControllerFullyVisible) hideController() else showController()
                }
                return true
            }

            override fun onDoubleTap(e: MotionEvent): Boolean {
                if (isLocked) return false
                playerView.player?.run {
                    playerView.togglePlayPause()
                } ?: return false
                return true
            }

            override fun onLongPress(e: MotionEvent) {
                if (isLocked) return
                if (playerView.player?.isPlaying == false) return
                if (currentGestureAction == null) {
                    currentGestureAction = GestureAction.FastPlayBack
                    currentPlaySpeed = playerView.player?.playbackParameters?.speed
                }
                if (currentGestureAction != GestureAction.FastPlayBack) return
                playerView.hideController()
                playerView.player?.setPlaybackSpeed(2f)
                playerPanel.showTopInfo(R.drawable.ic_player_fast_forward, "2倍速~")
            }
        }
    )

    private val seekGestureDetector = GestureDetector(
        context, object : GestureDetector.SimpleOnGestureListener() {
            override fun onScroll(
                firstEvent: MotionEvent?,
                currentEvent: MotionEvent,
                distanceX: Float,
                distanceY: Float
            ): Boolean {
                if (isLocked) return false
                if (firstEvent == null || inExclusionArea(firstEvent)) return false
                val player = playerView.player ?: return false
                if (!player.isReadyToPlay()) return false
                if (abs(distanceX / distanceY) < 2) return false

                if (currentGestureAction == null) {
                    timeBar.seekBegan()
                    playerView.controllerAutoShow = false
                    playerView.controllerShowTimeoutMs = Int.MAX_VALUE
                    playerView.showController()
                    seekStartPos = player.currentPosition
                    currentGestureAction = GestureAction.Seek
                }
                if (currentGestureAction != GestureAction.Seek) return false

                val maxSeekMillis = player.duration.coerceAtMost(SEEK_LINE_TIME)
                val movePosition = (((currentEvent.x - firstEvent.x)
                        / playerView.width) * maxSeekMillis).toLong()
                seekEndedPos = ((seekStartPos!!) + movePosition).coerceIn(0L, player.duration)
                timeBar.seekPosition(seekEndedPos!!)

                playerPanel.showInfo(
                    seekEndedPos!!.formatDurationMillis(),
                    movePosition.formatDurationMillis(false)
                )
                return false
            }
        }
    )

    private val volumeAndBrightnessGestureDetector = GestureDetector(
        playerView.context,
        object : GestureDetector.SimpleOnGestureListener() {
            override fun onScroll(
                firstEvent: MotionEvent?,
                currentEvent: MotionEvent,
                distanceX: Float,
                distanceY: Float,
            ): Boolean {
                if (isLocked) return false
                if (firstEvent == null) return false
                if (inExclusionArea(firstEvent)) return false
                if (abs(distanceY / distanceX) < 2) return false

                if (currentGestureAction == null) {
                    currentGestureAction = GestureAction.Swipe
                    playerView.hideController()
                }
                if (currentGestureAction != GestureAction.Swipe) return false

                val viewCenterX = playerView.measuredWidth / 2
                val distanceFull = playerView.measuredHeight * FULL_SWIPE_RANGE_SCREEN_RATIO
                val ratioChange = distanceY / distanceFull

                if (firstEvent.x.toInt() > viewCenterX) {
                    val change = ratioChange * volumeManager.maxVolume
                    volumeManager.setVolume(volumeManager.currentVolume + change)
                    playerPanel.showVolumeInfo(volumeManager)
                } else {
                    val change = ratioChange * brightnessManager.maxBrightness
                    brightnessManager.setBrightness(brightnessManager.currentBrightness + change)
                    playerPanel.showBrightnessInfo(brightnessManager)
                }
                return true
            }
        }
    )

    private fun release() {
        playerPanel.hideVolumeInfo()
        playerPanel.hideBrightnessInfo()
        playerPanel.hideTopInfo(0)
        playerPanel.hideInfo(0)

        playerView.controllerAutoShow = true
        playerView.controllerShowTimeoutMs = AUTO_SHOW_CONTROLLER

        currentPlaySpeed?.let {
            playerView.player?.setPlaybackSpeed(it)
        }
        currentPlaySpeed = null

        if (seekStartWithPlaying) {
            playerView.player?.play()
        }
        seekStartWithPlaying = false

        if (currentGestureAction == GestureAction.Seek) {
            timeBar.seekEnded()
            seekEndedPos?.let { toPos ->
                playerView.player?.run {
                    if (toPos < currentPosition) {
                        seekBack(toPos, true)
                    } else {
                        seekForward(toPos, true)
                    }
                }
            }
        }
        seekStartPos = null
        seekEndedPos = null
        currentGestureAction = null
    }

    init {
        playerView.setOnTouchListener { _, event ->
            when (event.pointerCount) {
                1 -> {
                    tapGestureDetector.onTouchEvent(event)
                    seekGestureDetector.onTouchEvent(event)
                    volumeAndBrightnessGestureDetector.onTouchEvent(event)
                }
            }
            if (event.action == MotionEvent.ACTION_UP) {
                release()
            }
            true
        }
    }

    private enum class GestureAction {
        FastPlayBack,
        Swipe,
        Seek
    }

    companion object {
        const val FULL_SWIPE_RANGE_SCREEN_RATIO = 0.66f
        const val GESTURE_EXCLUSION_AREA = 20f
        const val SEEK_LINE_TIME = 5 * 60 * 1000L // 滑动最长移动5分钟
        const val AUTO_SHOW_CONTROLLER = 3000

        fun setupGesture(
            uiBinding: AgeExoPlayerUIBinding,
            volumeManager: VolumeManager,
            brightnessManager: BrightnessManager
        ) : AgeExoPlayerGestureHelper {
            return AgeExoPlayerGestureHelper(uiBinding, volumeManager, brightnessManager)
        }
    }

    // ***************************************************************************
    // Extensions

    private fun inExclusionArea(firstEvent: MotionEvent): Boolean {
        val gestureExclusionBorder = GESTURE_EXCLUSION_AREA.dip
        return firstEvent.y < gestureExclusionBorder
                || firstEvent.y > playerView.height - gestureExclusionBorder
                || firstEvent.x < gestureExclusionBorder
                || firstEvent.x > playerView.width - gestureExclusionBorder
    }

    @SuppressLint("DefaultLocale")
    fun Long.formatDurationMillis(ignorePositiveUnit: Boolean = true): String {
        val unit = if (this < 0) "-" else if (ignorePositiveUnit) "" else "+"
        val safe = abs(this)
        val hours = TimeUnit.MILLISECONDS.toHours(safe)
        val minutes = TimeUnit.MILLISECONDS.toMinutes(safe) - TimeUnit.HOURS.toMinutes(hours)
        val seconds = TimeUnit.MILLISECONDS.toSeconds(safe) -
                TimeUnit.MINUTES.toSeconds(minutes) -
                TimeUnit.HOURS.toSeconds(hours)
        return if (hours > 0) {
            "$unit${String.format("%02d:%02d:%02d", hours, minutes, seconds)}"
        } else {
            "$unit${String.format("%02d:%02d", minutes, seconds)}"
        }
    }

    private fun PlayerView.togglePlayPause() {
        controllerAutoShow = isControllerFullyVisible
        if (player?.isPlaying == true) {
            player?.pause()
        } else {
            player?.play()
        }
    }

    private fun Player.isReadyToPlay(): Boolean {
        return playbackState == Player.STATE_READY
                || playbackState == Player.STATE_ENDED
    }

    private fun Player.setSeekParameters(seekParameters: SeekParameters) {
        when (this) {
            is ExoPlayer -> this.setSeekParameters(seekParameters)
        }
    }

    private fun Player.seekBack(positionMs: Long, shouldFastSeek: Boolean = false) {
        setSeekParameters(if (shouldFastSeek) SeekParameters.PREVIOUS_SYNC else SeekParameters.DEFAULT)
        this.seekTo(positionMs)
    }

    private fun Player.seekForward(positionMs: Long, shouldFastSeek: Boolean = false) {
        setSeekParameters(if (shouldFastSeek) SeekParameters.NEXT_SYNC else SeekParameters.DEFAULT)
        this.seekTo(positionMs)
    }

}